use crate::ast::*;
use crate::error::{CompilerError, Result};
use inkwell::types::BasicType;
use inkwell::values::BasicValueEnum;

use super::LLVMBackend;

impl<'ctx> LLVMBackend<'ctx> {
    /// Generate pattern condition for match expressions
    pub(super) fn generate_pattern_condition(
        &mut self,
        match_value: BasicValueEnum<'ctx>,
        pattern: &Pattern,
        guard: &Option<Expression>,
    ) -> Result<inkwell::values::IntValue<'ctx>> {
        let pattern_condition = match pattern {
            Pattern::Literal(lit) => {
                let pattern_value = self.generate_literal(lit)?;
                match (match_value, pattern_value) {
                    (BasicValueEnum::IntValue(mv), BasicValueEnum::IntValue(pv)) => {
                        let result = self.builder.build_int_compare(
                            inkwell::IntPredicate::EQ,
                            mv,
                            pv,
                            "pattern_eq",
                        )?;
                        let extended = self.builder.build_int_z_extend(
                            result,
                            self.context.i32_type(),
                            "pattern_condition",
                        )?;
                        Ok::<inkwell::values::IntValue<'ctx>, CompilerError>(extended)
                    }
                    _ => return Err(CompilerError::internal("Pattern type mismatch")),
                }
            }
            Pattern::Wildcard => {
                // Wildcard always matches
                Ok(self.context.i32_type().const_int(1, false))
            }
            Pattern::Or(patterns) => {
                // OR pattern - any of the patterns can match
                let mut or_result = self.context.i32_type().const_int(0, false);
                for sub_pattern in patterns {
                    let sub_condition =
                        self.generate_pattern_condition(match_value, sub_pattern, &None)?;
                    let sub_condition_bool = if sub_condition.get_type() == self.context.bool_type()
                    {
                        sub_condition
                    } else {
                        let zero = sub_condition.get_type().const_int(0, false);
                        self.builder.build_int_compare(
                            inkwell::IntPredicate::NE,
                            sub_condition,
                            zero,
                            "sub_condition_bool",
                        )?
                    };
                    or_result = self.builder.build_or(
                        or_result,
                        self.builder.build_int_z_extend(
                            sub_condition_bool,
                            self.context.i32_type(),
                            "sub_condition_ext",
                        )?,
                        "or_result",
                    )?;
                }
                Ok(or_result)
            }
            Pattern::Enum(enum_pattern) => {
                // Enum pattern: match variant and bind variables
                self.generate_enum_pattern_condition(match_value, enum_pattern)
            }
            _ => {
                return Err(CompilerError::internal(&format!(
                    "Unsupported pattern type: {:?}",
                    pattern
                )));
            }
        }?;

        // Apply guard condition if present
        if let Some(guard_expr) = guard {
            let guard_condition = self.generate_expression(guard_expr)?;
            let guard_condition_int = match guard_condition {
                BasicValueEnum::IntValue(int_val) => int_val,
                _ => {
                    return Err(CompilerError::internal(
                        "Guard condition must be an integer",
                    ))
                }
            };

            // Combine pattern condition and guard condition with AND
            let pattern_condition_bool = if pattern_condition.get_type() == self.context.bool_type()
            {
                pattern_condition
            } else {
                let zero = pattern_condition.get_type().const_int(0, false);
                self.builder.build_int_compare(
                    inkwell::IntPredicate::NE,
                    pattern_condition,
                    zero,
                    "pattern_condition_bool",
                )?
            };
            let guard_condition_bool = if guard_condition_int.get_type() == self.context.bool_type()
            {
                guard_condition_int
            } else {
                let zero = guard_condition_int.get_type().const_int(0, false);
                self.builder.build_int_compare(
                    inkwell::IntPredicate::NE,
                    guard_condition_int,
                    zero,
                    "guard_condition_bool",
                )?
            };
            let combined_condition = self.builder.build_and(
                pattern_condition_bool,
                guard_condition_bool,
                "combined_condition",
            )?;
            Ok(self.builder.build_int_z_extend(
                combined_condition,
                self.context.i32_type(),
                "final_condition",
            )?)
        } else {
            Ok(pattern_condition)
        }
    }

    /// Generate condition for enum pattern matching
    /// Returns true if the enum value matches the pattern and binds variables
    fn generate_enum_pattern_condition(
        &mut self,
        match_value: BasicValueEnum<'ctx>,
        enum_pattern: &EnumPattern,
    ) -> Result<inkwell::values::IntValue<'ctx>> {
        let enum_name = &enum_pattern.enum_name;
        let variant_name = &enum_pattern.variant_name;

        // Get enum declaration
        let enum_decl = self
            .enum_declarations
            .get(enum_name)
            .ok_or_else(|| CompilerError::internal(&format!("Enum '{}' not found", enum_name)))?
            .clone();

        // Find the variant and its index
        let (variant_index, variant) = enum_decl
            .variants
            .iter()
            .enumerate()
            .find(|(_, v)| v.name == *variant_name)
            .ok_or_else(|| {
                CompilerError::internal(&format!(
                    "Variant '{}::{}' not found",
                    enum_name, variant_name
                ))
            })?;

        // Extract tag from enum value
        let tag_value = self.extract_enum_tag(match_value, enum_name)?;

        // Compare tag with expected variant index
        let expected_tag = self
            .context
            .i32_type()
            .const_int(variant_index as u64, false);
        let tag_matches = self.builder.build_int_compare(
            inkwell::IntPredicate::EQ,
            tag_value.into_int_value(),
            expected_tag,
            "tag_matches",
        )?;

        // If this variant has data and patterns to bind, we need to extract and bind the data
        if let Some(variant_data) = &variant.data {
            if !enum_pattern.patterns.is_empty() {
                // Store match_value to stack so we can get a pointer
                let match_value_ptr = self
                    .builder
                    .build_alloca(match_value.get_type(), "match_value_temp")?;
                self.builder.build_store(match_value_ptr, match_value)?;

                // Get pointer to data array
                let data_ptr = self.extract_enum_data_ptr(match_value_ptr, enum_name)?;

                // Extract and bind each field
                match variant_data {
                    EnumVariantData::Tuple(types) => {
                        if enum_pattern.patterns.len() != types.len() {
                            return Err(CompilerError::internal(&format!(
                                "Pattern has {} variables but variant has {} fields",
                                enum_pattern.patterns.len(),
                                types.len()
                            )));
                        }

                        let mut offset = 0;
                        for (i, (ty, pattern)) in
                            types.iter().zip(enum_pattern.patterns.iter()).enumerate()
                        {
                            // Only bind if it's a Variable pattern
                            if let Pattern::Variable(var_name) = pattern {
                                let field_llvm_type =
                                    self.nrc_type_to_llvm_type(&Some(ty.clone()))?;

                                // Calculate pointer to this field
                                let field_ptr = unsafe {
                                    self.builder.build_gep(
                                        self.context.i8_type(),
                                        data_ptr,
                                        &[self.context.i32_type().const_int(offset as u64, false)],
                                        &format!("field_{}_ptr", i),
                                    )?
                                };

                                // Cast to correct type
                                let typed_ptr = self.builder.build_pointer_cast(
                                    field_ptr,
                                    field_llvm_type.ptr_type(inkwell::AddressSpace::default()),
                                    &format!("field_{}_typed", i),
                                )?;

                                // Load the value
                                let field_value = self.builder.build_load(
                                    field_llvm_type,
                                    typed_ptr,
                                    var_name,
                                )?;

                                // Create alloca for the bound variable
                                let var_alloca =
                                    self.builder.build_alloca(field_llvm_type, var_name)?;
                                self.builder.build_store(var_alloca, field_value)?;

                                // Add to variable map
                                self.variable_map
                                    .insert(var_name.clone(), var_alloca.into());
                                self.variable_type_map.insert(var_name.clone(), ty.clone());

                                offset += self.get_llvm_type_size(field_llvm_type);
                            }
                            // TODO: Support other pattern types (Wildcard, Literal, etc.)
                        }
                    }
                    EnumVariantData::Struct(_fields) => {
                        return Err(CompilerError::internal(
                            "Struct-style enum patterns not yet implemented",
                        ));
                    }
                }
            }
        }

        // Return the comparison result
        Ok(self.builder.build_int_z_extend(
            tag_matches,
            self.context.i32_type(),
            "pattern_result",
        )?)
    }
}
